/*
    wmanager.cpp. SunDog window manager
    This file is part of the SunDog engine.
    Copyright (C) 2002 - 2009 Alex Zolotov <nightradio@gmail.com>
*/

#include "../../main/user_code.h"
#include "../../core/debug.h"
#include "../../time/timemanager.h"
#include "../../utils/utils.h"
#include "../wmanager.h"
#include <stdarg.h>

#ifdef PALMOS
    #include <PalmOS.h>
    #include "palm_functions.h"
#endif

//Fonts (WIN1251)
#include "fonts.h"
sundog_image *g_font0_img = 0;
sundog_image *g_font1_img = 0;

sundog_event g_sundog_null_evt;

//################################
//## MAIN FUNCTIONS:            ##
//################################

int win_init( 
    const UTF8_CHAR *windowname, 
    int xsize, 
    int ysize, 
    int flags, 
    int argc, 
    UTF8_CHAR **argv,
    window_manager *wm )
{
    int retval = 0;

    wm->wm_initialized = 0;

    wm->argc = argc;
    wm->argv = argv;

    wm->events_count = 0;
    wm->current_event_num = 0;
    mem_set( &g_sundog_null_evt, sizeof( g_sundog_null_evt ), 0 );
    sundog_mutex_init( &wm->events_mutex, 0 );

    wm->flags = flags;

    wm->exit_request = 0;
    wm->exit_code = 0;

    wm->root_win = 0;

    wm->trash = 0;

    wm->window_counter = 0;

    mem_set( wm->timers, sizeof( wm->timers ), 0 );
    wm->timers_num = 0;

    wm->screen_lock_counter = 0;
    wm->screen_is_active = 0;
    wm->screen_changed = 0;

    wm->pen_x = -1;
    wm->pen_y = -1;
    wm->focus_win = 0;

    wm->handler_of_unhandled_events = 0;

    wm->g_screen = 0;
    wm->cur_window = 0;
    wm->cur_font_color = COLORMASK;
    wm->cur_font_draw_bgcolor = false; 
    wm->cur_font_bgcolor = 0;
    wm->cur_transparency = 255;
    wm->cur_font_zoom = 256;

    //SECOND STEP: SCREEN SIZE SETTING and some device init
    dprint( "MAIN: device start\n" );

    wm->fb_offset = 0;
    wm->fb_xpitch = 1;
    wm->fb_ypitch = 0;
    wm->fb_landscape = 0;
#ifdef OPENGL
    wm->gl_double_buffer = 1;
#endif
    int err = device_start( windowname, xsize, ysize, flags, wm ); //DEVICE DEPENDENT PART
    if( err )
    {
	dprint( "Error in device_start() %d\n", err );
	return 1; //Error
    }

    dprint( "MAIN: screen_xsize = %d\n", wm->screen_xsize );
    dprint( "MAIN: screen_ysize = %d\n", wm->screen_ysize );
      
    dprint( "MAIN: system palette init\n" );
    for( int c = 0; c < 16; c++ )
    {
	int r = c * 16;
	int g = c * 16;
	int b = c * 16;
	if( r >= 255 ) r = 255;
	if( g >= 255 ) g = 255;
	if( b >= 255 ) b = 255;
	if( r < 0 ) r = 0;
	if( g < 0 ) g = 0;
	if( b < 0 ) b = 0;
	wm->colors[ c ] = get_color( r, g, b );
    }
    wm->white = get_color( 255, 255, 255 );
    wm->black = get_color( 0, 0, 0 );
#ifdef GRAYSCALE
    wm->green = get_color( 64, 255, 64 );
    wm->yellow = get_color( 250, 250, 250 );
    wm->red = get_color( 230, 230, 230 );
#else
    wm->green = get_color( 0, 255, 0 );
    wm->yellow = get_color( 255, 255, 0 );
    wm->red = get_color( 255, 0, 0 );
#endif
    wm->dialog_color = wm->white;
    wm->decorator_color = blend( wm->white, wm->black, 44 );
    wm->decorator_border = blend( wm->black, wm->decorator_color, 90 );
    wm->button_color = wm->colors[ 12 ];
    wm->menu_color = blend( wm->white, wm->black, 24 );
    wm->selection_color = blend( wm->white, wm->green, 145 );
    wm->text_background = blend( wm->dialog_color, wm->black, 44 );
    wm->list_background = wm->text_background;
    wm->scroll_color = wm->colors[ 13 ];
    wm->scroll_background_color = wm->colors[ 11 ];

    wm->decor_border_size = 4;
    wm->decor_header_size = 14;
    wm->scrollbar_size = 18;
    wm->button_xsize = 70;
    if( wm->screen_xsize > 320 && wm->screen_ysize > 320 )
    {
	wm->gui_size_increment = 1;
    }
    else
    {
	wm->gui_size_increment = 0;
    }
    if( wm->gui_size_increment )
    {
	wm->decor_border_size += 1;
	wm->decor_header_size += 1;
	wm->scrollbar_size += 4;
	wm->button_xsize += 20;
    }

    wm->default_font = 1;
#if defined(OPENGL) && !defined(FRAMEBUFFER)
    //Create texture with default 8x8 font:
    uchar *tmp = (uchar*)MEM_NEW( HEAP_DYNAMIC, 8 * 8 * 256 );
    int img_ptr = 0;
    for( int y = 0; y < 8 * 256; y++ )
    {
	uchar v = g_font0[ y ];
	if( y >= 8 * 255 ) v = 0xFF;
	for( int x = 0; x < 8; x++ )
	{
	    if( v & 128 ) 
		tmp[ img_ptr ] = 255;
	    else
		tmp[ img_ptr ] = 0;
	    v <<= 1;
	    img_ptr++;
	}
    }
    g_font0_img = new_image( 8, 8 * 256, tmp, 0, 0, 8, 8 * 256, IMAGE_ALPHA8, wm );
    mem_free( tmp );
#endif

    dprint( "MAIN: wmanager initialized\n" );

    wm->wm_initialized = 1;

    return retval;
}

void win_close( window_manager *wm )
{
    //Clear trash with a deleted windows:
    if( wm->trash )
    {
	for( unsigned int a = 0; a < mem_get_size( wm->trash ) / sizeof( WINDOWPTR ); a++ )
	{
	    if( wm->trash[ a ] ) 
	    {
		if( wm->trash[ a ]->x1com ) mem_free( wm->trash[ a ]->x1com );
		if( wm->trash[ a ]->y1com ) mem_free( wm->trash[ a ]->y1com );
		if( wm->trash[ a ]->x2com ) mem_free( wm->trash[ a ]->x2com );
		if( wm->trash[ a ]->y2com ) mem_free( wm->trash[ a ]->y2com );
		mem_free( wm->trash[ a ] );
	    }
	}
	mem_free( wm->trash );
    }

    if( g_font0_img )
	remove_image( g_font0_img );
    if( g_font1_img )
	remove_image( g_font1_img );

    if( wm->screen_lock_counter > 0 )
    {
	dprint( "MAIN: WARNING. Screen is still locked (%d)\n", wm->screen_lock_counter );
	while( wm->screen_lock_counter > 0 ) device_screen_unlock( 0, wm );
    }

    if( wm->g_screen )
	mem_free( wm->g_screen );

    device_end( wm ); //DEVICE DEPENDENT PART (defined in eventloop.h)

    sundog_mutex_destroy( &wm->events_mutex );
}

//################################
//## WINDOWS FUNCTIONS:         ##
//################################

WINDOWPTR get_from_trash( window_manager *wm );

WINDOWPTR new_window( 
    const UTF8_CHAR *name, 
    int x, 
    int y, 
    int xsize, 
    int ysize, 
    COLOR color, 
    WINDOWPTR parent, 
    int (*win_handler)( sundog_event*, window_manager* ),
    window_manager *wm )
{
    //Create window structure:
    WINDOWPTR win = get_from_trash( wm );
    if( win == 0 ) 
    {
	win = (WINDOWPTR)mem_new( HEAP_DYNAMIC, sizeof( WINDOW ), name, 0 );
	mem_set( win, sizeof( WINDOW ), 0 );
	win->id = (int16)wm->window_counter;
	wm->window_counter++;
    }

    //Setup properties:
    win->name = name;
    win->x = x;
    win->y = y;
    win->xsize = xsize;
    win->ysize = ysize;
    win->color = color;
    win->parent = parent;
    win->win_handler = ( int (*)( void*, void* ) )win_handler;
    win->click_time = time_ticks() - time_ticks_per_second() * 10;

    win->controllers_calculated = 0;
    win->controllers_defined = 0;

    win->action_handler = 0;
    win->handler_data = 0;
    win->action_result = 0;
    
    win->font = wm->default_font;

    //Start init:
    if( win_handler )
    {
	sundog_event evt;
	mem_set( &evt, sizeof( evt ), 0 );
	evt.win = win;
	evt.type = EVT_GETDATASIZE;
	int datasize = win_handler( &evt, wm );
	if( datasize > 0 )
	{
	    win->data = mem_new( HEAP_DYNAMIC, datasize, "win data", 0 );
	}
	evt.type = EVT_AFTERCREATE;
	win_handler( &evt, wm );
    }

    //Save it to window manager:
    if( wm->root_win == 0 )
        wm->root_win = win;
    add_child( parent, win, wm );
    return win;
}

void set_window_controller( WINDOWPTR win, int ctrl_num, window_manager *wm, ... )
{
    va_list p;
    va_start( p, wm );
    int ptr = 0;
    win->controllers_defined = 1;
    int *cmds = 0;
    switch( ctrl_num )
    {
	case 0: cmds = win->x1com; break;
	case 1: cmds = win->y1com; break;
	case 2: cmds = win->x2com; break;
	case 3: cmds = win->y2com; break;
    }
    if( cmds == 0 )
	cmds = (int*)MEM_NEW( HEAP_DYNAMIC, sizeof( int ) * 4 );
    while( 1 )
    {
	int command = va_arg( p, int );
	if( mem_get_size( cmds ) / sizeof( int ) <= ptr )
	    cmds = (int*)mem_resize( cmds, sizeof( int ) * ( ptr + 4 ) );
	cmds[ ptr ] = command; 
	if( command == CEND ) break;
	ptr++;
    }
    switch( ctrl_num )
    {
	case 0: win->x1com = cmds; break;
	case 1: win->y1com = cmds; break;
	case 2: win->x2com = cmds; break;
	case 3: win->y2com = cmds; break;
    }
    va_end( p );
}

void move_to_trash( WINDOWPTR win, window_manager *wm )
{
    if( win )
    {
	win->visible = 0;
	win->flags |= WIN_FLAG_TRASH;
	if( wm->trash == 0 )
	{
	    wm->trash = (WINDOWPTR*)MEM_NEW( HEAP_DYNAMIC, sizeof( WINDOWPTR ) * 16 );
	    mem_set( wm->trash, sizeof( WINDOWPTR ) * 16, 0 );
	    wm->trash[ 0 ] = win;
	}
	else
	{
	    unsigned int w = 0;
	    for( w = 0; w < mem_get_size( wm->trash ) / sizeof( WINDOWPTR ); w++ )
	    {
		if( wm->trash[ w ] == 0 ) break;
	    }
	    if( w < mem_get_size( wm->trash ) / sizeof( WINDOWPTR ) ) 
		wm->trash[ w ] = win;
	    else
	    {
		wm->trash = (WINDOWPTR*)mem_resize( wm->trash, mem_get_size( wm->trash ) + sizeof( WINDOWPTR ) * 16 );
		wm->trash[ w ] = win;
	    }
	}
    }
}

WINDOWPTR get_from_trash( window_manager *wm )
{
    if( wm->trash == 0 ) return 0;
    unsigned int w = 0;
    for( w = 0; w < mem_get_size( wm->trash ) / sizeof( WINDOWPTR ); w++ )
    {
	if( wm->trash[ w ] != 0 ) 
	{
	    WINDOWPTR win = wm->trash[ w ];
	    wm->trash[ w ] = 0;
	    win->id++;
	    win->flags = 0;
	    return win;
	}
    }
    return 0;
}

void remove_window( WINDOWPTR win, window_manager *wm )
{
    if( win )
    {
	if( win->flags & WIN_FLAG_TRASH )
	{
	    dprint( "ERROR: can't remove already removed window (%s)\n", win->name );
	    return;
	}
	if( win->win_handler )
	{
	    //Sent EVT_BEFORECLOSE to window handler:
	    sundog_event evt;
	    mem_set( &evt, sizeof( evt ), 0 );
	    evt.win = win;
	    evt.type = EVT_BEFORECLOSE;
	    win->win_handler( &evt, wm );
	}
	if( win->childs )
	{
	    //Remove childs:
	    while( win->childs_num )
		remove_window( win->childs[ 0 ], wm );
	    mem_free( win->childs );
	    win->childs = 0;
	}
	//Remove data:
	if( win->data ) 
	    mem_free( win->data );
	win->data = 0;
	//Remove region:
	if( win->reg ) 
	    GdDestroyRegion( win->reg );
	win->reg = 0;
	//Remove commands:
	if( win->x1com ) mem_free( win->x1com ); win->x1com = 0;
	if( win->y1com ) mem_free( win->y1com ); win->y1com = 0;
	if( win->x2com ) mem_free( win->x2com ); win->x2com = 0;
	if( win->y2com ) mem_free( win->y2com ); win->y2com = 0;
	//Remove window:
	if( win == wm->focus_win )
	    wm->focus_win = 0;
	if( win == wm->root_win )
	{
	    //It's root win:
	    move_to_trash( win, wm );
	    wm->root_win = 0;
	}
	else
	{
	    remove_child( win->parent, win, wm );
	    //Full remove:
	    move_to_trash( win, wm );
	}
    }
}

void add_child( WINDOWPTR win, WINDOWPTR child, window_manager *wm )
{
    if( win == 0 || child == 0 ) return;

    if( win->childs == 0 )
    {
	win->childs = (WINDOWPTR*)mem_new( HEAP_DYNAMIC, 4 * 4, "childs", 0 );
    }
    else
    {
	int old_size = mem_get_size( win->childs ) / 4;
	if( win->childs_num >= old_size )
	    win->childs = (WINDOWPTR*)mem_resize( win->childs, ( old_size + 4 ) * 4 );
    }
    win->childs[ win->childs_num ] = child;
    win->childs_num++;
    child->parent = win;
}

void remove_child( WINDOWPTR win, WINDOWPTR child, window_manager *wm )
{
    if( win == 0 || child == 0 ) return;

    //Remove link from parent window:
    int c;
    for( c = 0; c < win->childs_num; c++ )
    {
	if( win->childs[ c ] == child ) break;
    }
    if( c < win->childs_num )
    {
	for( ; c < win->childs_num - 1; c++ )
	{
	    win->childs[ c ] = win->childs[ c + 1 ];
	}
	win->childs_num--;
	child->parent = 0;
    }
}

void set_handler( WINDOWPTR win, int (*handler)(void*,WINDOWPTR,window_manager*), void *handler_data, window_manager *wm )
{
    if( win )
    {
	win->action_handler = (int (*)(void*,void*,void*)) handler;;
	win->handler_data = handler_data;
    }
}

void draw_window( WINDOWPTR win, window_manager *wm )
{
    win_draw_lock( win, wm );
    if( win && win->visible && win->reg )
    {
	if( win->reg->numRects )
	{
	    sundog_event evt;
	    mem_set( &evt, sizeof( evt ), 0 );
    	    evt.win = win;
	    evt.type = EVT_DRAW;
	    if( win->win_handler && win->win_handler( &evt, wm ) )
	    {
		//Draw event was handled
	    }
	    else
	    {
		win_draw_box( win, 0, 0, win->xsize, win->ysize, win->color, wm );
	    }
	}
	if( win->childs_num )
	{
	    for( int c = 0; c < win->childs_num; c++ )
	    {
		draw_window( win->childs[ c ], wm );
	    }
	}
    }
    win_draw_unlock( win, wm );
}

void show_window( WINDOWPTR win, window_manager *wm )
{
    if( win && ( win->flags & WIN_FLAG_ALWAYS_INVISIBLE ) == 0 )
    {
	win->visible = 1;
	for( int c = 0; c < win->childs_num; c++ )
	{
	    show_window( win->childs[ c ], wm );
	}
    }
}

void hide_window( WINDOWPTR win, window_manager *wm )
{
    if( win )
    {
	win->visible = 0;
	for( int c = 0; c < win->childs_num; c++ )
	{
	    hide_window( win->childs[ c ], wm );
	}
    }
}

void recalc_controllers( WINDOWPTR win, window_manager *wm );

void run_controller( WINDOWPTR win, int *c, int *val, int size, window_manager *wm )
{
    if( c == 0 ) return;
    int p = 0;
    WINDOWPTR other_win = 0;
    int a;
    int mode = 0;
    int perc = 0;
    int backval = 0;
    int brk = 0;
    while( !brk )
    {
	switch( c[ p ] )
	{
	    case CWIN: 
		p++;
		other_win = (WINDOWPTR)c[ p ];
		if( other_win->controllers_calculated == 0 ) recalc_controllers( other_win, wm );
		break;
	    case CX1:
		a = other_win->x;
		switch( mode )
		{
		    case 0: *val = a; break;
		    case 1: *val -= a; break;
		    case 2: *val += a; break;
		    case 3: if( *val > a ) *val = a; break;
		    case 4: if( *val < a ) *val = a; break;
		}
		mode = 0;
		perc = 0;
		break;
	    case CY1: 
		a = other_win->y;
		switch( mode )
		{
		    case 0: *val = a; break;
		    case 1: *val -= a; break;
		    case 2: *val += a; break;
		    case 3: if( *val > a ) *val = a; break;
		    case 4: if( *val < a ) *val = a; break;
		}
		mode = 0;
		perc = 0;
		break;
	    case CX2:
		a = other_win->x + other_win->xsize;
		switch( mode )
		{
		    case 0: *val = a; break;
		    case 1: *val -= a; break;
		    case 2: *val += a; break;
		    case 3: if( *val > a ) *val = a; break;
		    case 4: if( *val < a ) *val = a; break;
		}
		mode = 0;
		perc = 0;
		break;
	    case CY2: 
		a = other_win->y + other_win->ysize;
		switch( mode )
		{
		    case 0: *val = a; break;
		    case 1: *val -= a; break;
		    case 2: *val += a; break;
		    case 3: if( *val > a ) *val = a; break;
		    case 4: if( *val < a ) *val = a; break;
		}
		mode = 0;
		perc = 0;
		break;
	    case CSUB: mode = 1; break;
	    case CADD: mode = 2; break;
	    case CPERC: perc = 1; break;
	    case CBACKVAL0: backval = 0; break;
	    case CBACKVAL1: backval = 1; break;
	    case CMAXVAL: mode = 3; break;
	    case CMINVAL: mode = 4; break;
	    case CPUTR0: wm->creg0 = *val; break;
	    case CGETR0: *val = wm->creg0; break;
	    case CEND: brk = 1; break;
	    default: 
		switch( mode )
		{
		    case 0:
			if( perc )
			{
			    *val = ( c[ p ] * size ) / 100;
			}
			else
			{
			    if( backval )
			    {
				*val = size - c[ p ];
			    }
			    else
			    {
				*val = c[ p ]; //Simple number
			    }
			}
			break;
		
		    case 1:
			//Sub:
			if( perc )
			{
			    *val -= ( c[ p ] * size ) / 100;
			}
			else
			{
			    if( backval )
			    {
				*val -= size - c[ p ];
			    }
			    else
			    {
				*val -= c[ p ]; //Simple number
			    }
			}
			break;

		    case 2:
			//Add:
			if( perc )
			{
			    *val += ( c[ p ] * size ) / 100;
			}
			else
			{
			    if( backval )
			    {
				*val += size - c[ p ];
			    }
			    else
			    {
				*val += c[ p ]; //Simple number
			    }
			}
			break;
		
		    case 3:
			//Max val:
			if( perc )
			{
			    if( *val > ( c[ p ] * size ) / 100 ) *val = ( c[ p ] * size ) / 100;
			}
			else
			{
			    if( backval )
			    {
				if( *val > size - c[ p ] ) *val = size - c[ p ];
			    }
			    else
			    {
				if( *val > c[ p ] ) *val = c[ p ]; //Simple number
			    }
			}
			break;	
		    
		    case 4:
			//Min val:
			if( perc )
			{
			    if( *val < ( c[ p ] * size ) / 100 ) *val = ( c[ p ] * size ) / 100;
			}
			else
			{
			    if( backval )
			    {
				if( *val < size - c[ p ] ) *val = size - c[ p ];
			    }
			    else
			    {
				if( *val < c[ p ] ) *val = c[ p ]; //Simple number
			    }
			}
			break;
		}
		mode = 0;
		perc = 0;
		break;
	}
	p++;
    }
}

void recalc_controllers( WINDOWPTR win, window_manager *wm )
{
    if( win && win->controllers_calculated == 0 && win->parent )
    {
	if( !win->controllers_defined ) 
	    win->controllers_calculated = 1;
	else
	{
	    int x1 = win->x;
	    int y1 = win->y;
	    int x2 = win->x + win->xsize;
	    int y2 = win->y + win->ysize;
	    run_controller( win, win->x1com, &x1, win->parent->xsize, wm );
	    run_controller( win, win->x2com, &x2, win->parent->xsize, wm );
	    run_controller( win, win->y1com, &y1, win->parent->ysize, wm );
	    run_controller( win, win->y2com, &y2, win->parent->ysize, wm );
	    int temp;
	    if( x1 > x2 ) { temp = x1; x1 = x2; x2 = temp; }
	    if( y1 > y2 ) { temp = y1; y1 = y2; y2 = temp; }
	    win->x = x1;
	    win->y = y1;
	    win->xsize = x2 - x1;
	    win->ysize = y2 - y1;
	    win->controllers_calculated = 1;
	}
    }
}

void recalc_region( WINDOWPTR win, MWCLIPREGION *reg, int cut_x, int cut_y, int cut_x2, int cut_y2, int px, int py, window_manager *wm )
{
    if( !win->visible )
    {
	if( win->reg ) GdDestroyRegion( win->reg );
	win->reg = 0;
	return;
    }
    if( win->controllers_defined && win->controllers_calculated == 0 )
    {
	recalc_controllers( win, wm );
    }
    win->screen_x = win->x + px;
    win->screen_y = win->y + py;
    int x1 = win->x + px;
    int y1 = win->y + py;
    int x2 = win->x + px + win->xsize;
    int y2 = win->y + py + win->ysize;
    if( cut_x > x1 ) x1 = cut_x;
    if( cut_y > y1 ) y1 = cut_y;
    if( cut_x2 < x2 ) x2 = cut_x2;
    if( cut_y2 < y2 ) y2 = cut_y2;
    if( win->childs_num && !( x1 > x2 || y1 > y2 ) )
    {
	for( int c = win->childs_num - 1; c >= 0; c-- )
	{
	    recalc_region( 
		win->childs[ c ], 
		reg, 
		x1, y1, 
		x2, y2, 
		win->x + px, 
		win->y + py, 
		wm );
	}
    }
    if( win->reg == 0 )
    {
	if( x1 > x2 || y1 > y2 )
	    win->reg = GdAllocRegion();
	else
	    win->reg = GdAllocRectRegion( x1, y1, x2, y2 );
    }
    else
    {
	if( x1 > x2 || y1 > y2 )
	{
	    GdSetRectRegion( win->reg, 0, 0, 0, 0 );
	}
	else
	{
	    GdSetRectRegion( win->reg, x1, y1, x2, y2 );
	}
    }
    //reg - current invisible region.
    //Calc corrected win region:
    GdSubtractRegion( win->reg, win->reg, reg );
    //Calc corrected invisible region:
    GdUnionRegion( reg, reg, win->reg );
}

void clean_regions( WINDOWPTR win, window_manager *wm )
{
    if( win )
    {
	for( int c = 0; c < win->childs_num; c++ )
	    clean_regions( win->childs[ c ], wm );
	if( win->reg ) 
	{
	    GdSetRectRegion( win->reg, 0, 0, 0, 0 );
	}
	win->controllers_calculated = 0;
    }
}

void recalc_regions( window_manager *wm )
{
    MWCLIPREGION *reg = GdAllocRegion();
    if( wm->root_win )
    {
	//Control ALWAYS_ON_TOP flag: ===
	int size = wm->root_win->childs_num;
	for( int i = 0; i < size; i++ )
	{
	    WINDOWPTR win = wm->root_win->childs[ i ];
	    if( win && win->flags & WIN_FLAG_ALWAYS_ON_TOP && i < size - 1 )
	    {
		//Bring this window to front:
		for( int i2 = i; i2 < size - 1; i2++ )
		{
		    wm->root_win->childs[ i2 ] = wm->root_win->childs[ i2 + 1 ];
		}
		wm->root_win->childs[ size - 1 ] = win;
	    }
	}
	//===============================
	clean_regions( wm->root_win, wm );
	recalc_region( wm->root_win, reg, 0, 0, wm->screen_xsize, wm->screen_ysize, 0, 0, wm );
    }
    if( reg ) GdDestroyRegion( reg );
}

void set_focus_win( WINDOWPTR win, window_manager *wm )
{
    if( win && win->flags & WIN_FLAG_TRASH )
    {
	//This window removed by someone. But we can't focus on removed window.
	//So.. Just focus on NULL window:
	win = 0;
    }

    WINDOWPTR prev_focus_win = wm->focus_win;
    uint16 prev_focus_win_id = wm->focus_win_id;

    WINDOWPTR new_focus_win = win;
    uint16 new_focus_win_id = 0;
    if( win ) new_focus_win_id = win->id;

    wm->focus_win = new_focus_win;
    wm->focus_win_id = new_focus_win_id;

    if( prev_focus_win != new_focus_win || 
	( prev_focus_win == new_focus_win && prev_focus_win_id != new_focus_win_id ) )
    {
	//Focus changed:

	sundog_event evt2;
	mem_set( &evt2, sizeof( evt2 ), 0 );

        wm->prev_focus_win = prev_focus_win;
        wm->prev_focus_win_id = prev_focus_win_id;

	if( new_focus_win )
	{
	    //Send FOCUS event:
	    //In this event's handling user can remember previous focused window (wm->prev_focus_win)
	    evt2.type = EVT_FOCUS;
	    evt2.win = new_focus_win;
	    handle_event( &evt2, wm );
	}

	if( prev_focus_win != new_focus_win ) //Only, if prev_focus_win is not removed
	{
	    if( prev_focus_win )
	    {
		//Send UNFOCUS event:
		evt2.type = EVT_UNFOCUS;
		evt2.win = prev_focus_win;
		handle_event( &evt2, wm );
	    }
	}
    }
}

int find_focus_window( WINDOWPTR win, WINDOW **focus_win, window_manager *wm )
{
    if( win == 0 ) return 0;
    if( win->reg && GdPtInRegion( win->reg, wm->pen_x, wm->pen_y ) )
    {
	*focus_win = win;
	return 1;
    }
    for( int c = 0; c < win->childs_num; c++ )
    {
	if( find_focus_window( win->childs[ c ], focus_win, wm ) )
	    return 1;
    }
    return 0;
}

int send_event(
    WINDOWPTR win,
    int type,
    int flags,
    int x,
    int y,
    int key,
    int scancode,
    int pressure,
    UTF32_CHAR unicode,
    window_manager* wm )
{
    int retval = 0;
    
    sundog_mutex_lock( &wm->events_mutex );
    
    if( wm->events_count + 1 <= EVENTS )
    {
	//Get pointer to new event:
	int new_ptr = ( wm->current_event_num + wm->events_count ) & ( EVENTS - 1 );

	if( type == EVT_BUTTONDOWN || type == EVT_BUTTONUP )
	{
	    if( key >= 0x41 && key <= 0x5A ) //Capital
		key += 0x20; //Make it small
	}

	//Save new event to FIFO buffer:
	wm->events[ new_ptr ].type = (uint16)type;
	wm->events[ new_ptr ].time = time_ticks();
	wm->events[ new_ptr ].win = win;
	wm->events[ new_ptr ].flags = (uint16)flags;
	wm->events[ new_ptr ].x = (int16)x;
	wm->events[ new_ptr ].y = (int16)y;
	wm->events[ new_ptr ].key = (uint16)key;
	wm->events[ new_ptr ].pressure = (uint16)pressure;
	wm->events[ new_ptr ].unicode = unicode;

	//Increment number of unhandled events:
	volatile int new_event_count = wm->events_count + 1;
	wm->events_count = new_event_count;

	retval = 0;
    }
    else
    {
	retval = 1;
    }

    sundog_mutex_unlock( &wm->events_mutex );
    
    return retval;
}

int check_event( sundog_event *evt, window_manager *wm )
{
    if( evt == 0 ) return 1;

    if( evt->win == 0 )
    {
	if( !wm->wm_initialized ) return 1;
	
	if( evt->type == EVT_MOUSEBUTTONDOWN ||
	    evt->type == EVT_MOUSEBUTTONUP ||
	    evt->type == EVT_MOUSEMOVE )
	{
	    if( evt->x < 0 ) return 1;
	    if( evt->y < 0 ) return 1;
	    if( evt->x >= wm->screen_xsize ) return 1;
	    if( evt->y >= wm->screen_ysize ) return 1;
	    wm->pen_x = evt->x;
	    wm->pen_y = evt->y;
	    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	    if( wm->last_unfocused_window )
	    {
		evt->win = wm->last_unfocused_window;
		if( evt->type == EVT_MOUSEBUTTONUP )
		{
		    wm->last_unfocused_window = 0;
		}
		return 0;
	    }
	    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	    if( evt->type == EVT_MOUSEBUTTONDOWN )
	    { //If mouse click:
		if( evt->key & MOUSE_BUTTON_SCROLLUP ||
		    evt->key & MOUSE_BUTTON_SCROLLDOWN )
		{
		    //Mouse scroll up/down...
		    WINDOWPTR scroll_win = 0;
		    if( find_focus_window( wm->root_win, &scroll_win, wm ) )
		    {
			evt->win = scroll_win;
			return 0;
		    }
		    else
		    {
			//Window not found under the pointer:
			return 1;
		    }
		}
		else
		{
		    //Mouse click on some window...
		    WINDOWPTR focus_win = 0;
		    if( find_focus_window( wm->root_win, &focus_win, wm ) )
		    {
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			if( focus_win->flags & WIN_FLAG_ALWAYS_UNFOCUSED )
			{
			    evt->win = focus_win;
			    wm->last_unfocused_window = focus_win;
			    return 0;
			}
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			set_focus_win( focus_win, wm );
		    }
		    else
		    {
			//Window not found under the pointer:
			return 1;
		    }
		}
	    }
	}
	if( wm->focus_win )
	{
	    //Set pointer to window:
	    evt->win = wm->focus_win;

	    if( evt->type == EVT_MOUSEBUTTONDOWN && 
		( evt->time - wm->focus_win->click_time ) < ( DOUBLE_CLICK_PERIOD * time_ticks_per_second() ) / 1000 )
	    {
		evt->type = EVT_MOUSEDOUBLECLICK;
		wm->focus_win->click_time = evt->time - time_ticks_per_second() * 10; //Reset click time
	    }
	    if( evt->type == EVT_MOUSEBUTTONDOWN ) wm->focus_win->click_time = evt->time;
	}
    }

    return 0;
}

void handle_event( sundog_event *evt, window_manager *wm )
{
    if( evt->type == EVT_MOUSEDOUBLECLICK )
    {
	evt->type = EVT_MOUSEBUTTONDOWN;
	handle_event( evt, wm );
	evt->type = EVT_MOUSEDOUBLECLICK;
    }
    if( !user_event_handler( evt, wm ) || ( evt->flags & EVT_FLAG_AC ) )
    {
	//Event hot handled by simple event handler.
	//Send it to window:
	if( handle_event_by_window( evt, wm ) == 0 )
	{
	    evt->win = wm->handler_of_unhandled_events;
	    handle_event_by_window( evt, wm );
	}
    }
}

int handle_event_by_window( sundog_event *evt, window_manager *wm )
{
    int retval = 0;
    WINDOWPTR win = evt->win;
    if( win )
    {
	if( win->flags & WIN_FLAG_TRASH )
	{
	    dprint( "ERROR: can't handle event by removed window (%s)\n", win->name );
	    retval = 1;
	}
	else
	if( evt->type == EVT_DRAW ) 
	{
	    draw_window( win, wm );
	    retval = 1;
	}
	else
	{
	    if( win->win_handler )
	    {
		if( !win->win_handler( evt, wm ) || ( evt->flags & EVT_FLAG_AC ) )
		{
		    //Send event to children:
		    for( int c = 0; c < win->childs_num; c++ )
		    {
			evt->win = win->childs[ c ];
			if( handle_event_by_window( evt, wm ) && !( evt->flags & EVT_FLAG_AC ) )
			{
			    evt->win = win;
			    retval = 1;
			    goto end_of_handle;
			}
		    }
		    evt->win = win;
		}
		else
		{
		    retval = 1;
		    goto end_of_handle;
		}
	    }
end_of_handle:
	    if( win == wm->root_win )
	    {
		if( evt->type == EVT_SCREENRESIZE )
		{
		    //On screen resize:
		    recalc_regions( wm );
		    draw_window( wm->root_win, wm );
		}
	    }
	}
    }
    return retval;
}

int EVENT_LOOP_BEGIN( sundog_event *evt, window_manager *wm )
{
    int rv = 0;
    if( wm->timers_num )
    {
	ticks_t cur_time = time_ticks();
	for( int t = 0; t < TIMERS; t++ )
	{
	    if( wm->timers[ t ].handler )
	    {
		if( wm->timers[ t ].deadline <= cur_time )
		{
		    wm->timers[ t ].handler( wm->timers[ t ].data, &wm->timers[ t ], wm ); 
		    wm->timers[ t ].deadline += wm->timers[ t ].delay;
		}
	    }
	}
    }
    device_event_handler( wm );
    if( wm->events_count )
    {
	sundog_mutex_lock( &wm->events_mutex );

	//There are unhandled events:
	//Copy current event to "evt" buffer (prepare it for handling):
	mem_copy( evt, &wm->events[ wm->current_event_num ], sizeof( sundog_event ) );
	//This event will be handled. So decrement count of events:
	wm->events_count--;
	//And increment FIFO pointer:
	wm->current_event_num = ( wm->current_event_num + 1 ) & ( EVENTS - 1 );

	sundog_mutex_unlock( &wm->events_mutex );
	
	//Check the event and handle it:
	if( check_event( evt, wm ) == 0 )
	{
	    handle_event( evt, wm );
	    rv = 1;
	}
    }
    return rv;
}

int EVENT_LOOP_END( window_manager *wm )
{
    user_event_handler( &g_sundog_null_evt, wm );
    device_redraw_framebuffer( wm );
    if( wm->exit_request ) return 1;
    return 0;
}

int add_timer( void (*handler)( void*, sundog_timer*, window_manager* ), void *data, ticks_t delay, window_manager *wm )
{
    int t = -1;
    for( t = 0; t < TIMERS; t++ )
    {
	if( wm->timers[ t ].handler == 0 )
	{
	    break;
	}
    }
    if( t < TIMERS )
    {
	wm->timers[ t ].handler = handler;
	wm->timers[ t ].data = data;
	wm->timers[ t ].deadline = time_ticks() + delay;
	wm->timers[ t ].delay = delay;
	wm->timers_num++;
    }
    else
    {
	t = -1;
    }
    return t;
}

void remove_timer( int timer, window_manager *wm )
{
    if( timer >= 0 && timer < TIMERS )
    {
	if( wm->timers[ timer ].handler )
	{
	    wm->timers[ timer ].handler = 0;
	    wm->timers_num--;
	}
    }
}

//################################
//## WINDOWS DECORATIONS:       ##
//################################

#define DRAG_LEFT	1
#define DRAG_RIGHT	2
#define DRAG_TOP	4
#define DRAG_BOTTOM	8
#define DRAG_MOVE	16
struct decorator_data
{
    int start_win_x;
    int start_win_y;
    int start_win_xs;
    int start_win_ys;
    int start_pen_x;
    int start_pen_y;
    int drag_mode;
    int fullscreen;
    int prev_x;
    int prev_y;
    int prev_xsize;
    int prev_ysize;
};

int decorator_handler( sundog_event *evt, window_manager *wm )
{
    int retval = 0;
    WINDOWPTR win = evt->win;
    decorator_data *data = (decorator_data*)win->data;
    int dx, dy;
    int rx = evt->x - win->screen_x;
    int ry = evt->y - win->screen_y;
    switch( evt->type )
    {
	case EVT_GETDATASIZE:
	    retval = sizeof( decorator_data );
	    break;
	case EVT_AFTERCREATE:
	    data->fullscreen = 0;
	    break;
	case EVT_MOUSEDOUBLECLICK:
	    if( evt->key == MOUSE_BUTTON_LEFT )
	    {
		//Make fullscreen:
		if( data->fullscreen == 1 )
		{
		    win->x = data->prev_x;
		    win->y = data->prev_y;
		    win->xsize = data->prev_xsize;
		    win->ysize = data->prev_ysize;
		}
		else
		{
		    data->prev_x = win->x;
		    data->prev_y = win->y;
		    data->prev_xsize = win->xsize;
		    data->prev_ysize = win->ysize;
		    win->x = 0; 
		    win->y = 0;
		    win->xsize = win->parent->xsize;
		    win->ysize = win->parent->ysize;
		}
		win->childs[ 0 ]->xsize = win->xsize - DECOR_BORDER_SIZE * 2;
		win->childs[ 0 ]->ysize = win->ysize - DECOR_BORDER_SIZE * 2 - DECOR_HEADER_SIZE;
		data->fullscreen ^= 1;
		data->drag_mode = 0;
		recalc_regions( wm );
		draw_window( wm->root_win, wm );
	    }
	    retval = 1;
	    break;
	case EVT_MOUSEBUTTONDOWN:
	    if( evt->key == MOUSE_BUTTON_LEFT )
	    {
		//Bring to front: ================================================
		int i;
		for( i = 0; i < win->parent->childs_num; i++ )
		{
		    if( win->parent->childs[ i ] == win ) break;
		}
		if( i < win->parent->childs_num - 1 )
		{
		    for( int i2 = i; i2 < win->parent->childs_num - 1; i2++ )
			win->parent->childs[ i2 ] = win->parent->childs[ i2 + 1 ];
		    win->parent->childs[ win->parent->childs_num - 1 ] = win;
		    recalc_regions( wm );
		}
		//================================================================
		data->start_pen_x = evt->x;
		data->start_pen_y = evt->y;
		data->start_win_x = win->x;
		data->start_win_y = win->y;
		data->start_win_xs = win->xsize;
		data->start_win_ys = win->ysize;
		data->drag_mode = 0;
		if( ry >= DECOR_HEADER_SIZE )
		{
		    if( rx < DECOR_BORDER_SIZE + 8 ) data->drag_mode |= DRAG_LEFT;
		    if( rx >= win->xsize - DECOR_BORDER_SIZE - 8 ) data->drag_mode |= DRAG_RIGHT;
		    if( ry >= win->ysize - DECOR_BORDER_SIZE - 8 ) data->drag_mode |= DRAG_BOTTOM;
		}
		else data->drag_mode = DRAG_MOVE;
		draw_window( win, wm );
	    }
	    retval = 1;
	    break;
	case EVT_MOUSEBUTTONUP:
	    data->drag_mode = 0;
	    retval = 1;
	    break;
	case EVT_MOUSEMOVE:
	    if( evt->key == MOUSE_BUTTON_LEFT )
	    {
		dx = evt->x - data->start_pen_x;
		dy = evt->y - data->start_pen_y;
		if( data->drag_mode == DRAG_MOVE )
		{
		    //Move:
		    int prev_x = win->x;
		    int prev_y = win->y;
		    win->x = data->start_win_x + dx;
		    win->y = data->start_win_y + dy;
		    if( prev_x != win->x || prev_y != win->y )
		    {
			if( data->fullscreen == 1 )
			{
			    win->xsize = data->prev_xsize;
			    win->ysize = data->prev_ysize;
			    data->fullscreen = 0;
			}
		    }
		}
		if( data->drag_mode & DRAG_LEFT )
		{
		    int prev_x = win->x;
		    int prev_xsize = win->xsize;
		    win->x = data->start_win_x + dx;
		    win->xsize = data->start_win_xs - dx;
		    if( prev_x != win->x || prev_xsize != win->xsize )
		    {
			if( win->xsize < 16 ) win->xsize = 16;
			data->fullscreen = 0;
		    }
		}
		if( data->drag_mode & DRAG_RIGHT )
		{
		    int prev_xsize = win->xsize;
		    win->xsize = data->start_win_xs + dx;
		    if( prev_xsize != win->xsize )
		    {
			if( win->xsize < 16 ) win->xsize = 16;
			data->fullscreen = 0;
		    }
		}
		if( data->drag_mode & DRAG_BOTTOM )
		{
		    int prev_ysize = win->ysize;
		    win->ysize = data->start_win_ys + dy;
		    if( prev_ysize != win->ysize )
		    {
			if( win->ysize < 16 ) win->ysize = 16;
			data->fullscreen = 0;
		    }
		}
		if( win->childs_num )
		{
		    win->childs[ 0 ]->xsize = win->xsize - DECOR_BORDER_SIZE * 2;
		    win->childs[ 0 ]->ysize = win->ysize - DECOR_BORDER_SIZE * 2 - DECOR_HEADER_SIZE;
		}
		recalc_regions( wm );
		draw_window( wm->root_win, wm );
	    }
	    retval = 1;
	    break;
	case EVT_DRAW:
	    win_draw_lock( win, wm );
	    win_draw_box( win, 0, 0, win->xsize, win->ysize, win->color, wm );
	    if( 0 )
	    {
		int ssize = string_size( win, win->childs[0]->name, wm );
		if( ssize > 0 )
		{
		    for( int ll = 0; ll < 8; ll++ )
			win_draw_line( win, ssize + 8 + ll, ll, win->xsize, ll, blend( wm->white, win->color, ll * 32 ), wm );
		}
		else
		{
		    for( int ll = 0; ll < 8; ll++ )
			win_draw_line( win, 0, ll, win->xsize, ll, blend( wm->white, win->color, ll * 32 ), wm );
		}
	    }
	    if( win->childs_num )
	    {
		win_draw_string( win, win->childs[ 0 ]->name, DECOR_BORDER_SIZE, ( DECOR_HEADER_SIZE + DECOR_BORDER_SIZE - char_y_size( win, wm ) ) / 2, wm->black, win->color, wm );
		win_draw_frame( win, 0, 0, win->xsize, win->ysize, wm->decorator_border, wm );
	    }
	    else
	    {
		//No childs more :( Empty decorator. Lets remove it:
		remove_window( win, wm );
		recalc_regions( wm );
		draw_window( wm->root_win, wm );
	    }
	    win_draw_unlock( win, wm );
	    retval = 1;
	    break;
    }
    return retval;
}

WINDOWPTR new_window_with_decorator( 
    const UTF8_CHAR *name, 
    int x, 
    int y, 
    int xsize, 
    int ysize, 
    COLOR color,
    WINDOWPTR parent, 
    int (*win_handler)( sundog_event*, window_manager* ),
    int flags,
    window_manager *wm )
{
    int border = DECOR_BORDER_SIZE;
    int header = DECOR_HEADER_SIZE;
    x -= border;
    y -= border + header;
    int dec_xsize = xsize + border * 2;
    int dec_ysize = ysize + border * 2 + header;
    if( flags & DECOR_FLAG_CENTERED )
    {
	x = ( parent->xsize - dec_xsize ) / 2;
	y = ( parent->ysize - dec_ysize ) / 2;
    }
    if( flags & DECOR_FLAG_CHECK_SIZE )
    {
	if( x < 0 )
	{
	    dec_xsize -= ( -x );
	    x = 0;
	}
	if( y < 0 )
	{
	    dec_ysize -= ( -y );
	    y = 0;
	}
	if( x + dec_xsize > parent->xsize )
	{
	    dec_xsize -= ( ( x + dec_xsize ) - parent->xsize );
	}
	if( y + dec_ysize > parent->ysize )
	{
	    dec_ysize -= ( ( y + dec_ysize ) - parent->ysize );
	}
    }
    WINDOWPTR dec = new_window( 
	"decorator", 
	x, y,
	dec_xsize, dec_ysize,
	wm->decorator_color,
	parent,
	decorator_handler,
	wm );
    xsize = dec_xsize - border * 2;
    ysize = dec_ysize - ( header + border * 2 );
    WINDOWPTR win = new_window( name, border, header + border, xsize, ysize, color, dec, win_handler, wm );
    return dec;
}

//################################
//## DRAWING FUNCTIONS:         ##
//################################

int char_x_size( WINDOWPTR win, UTF32_CHAR c, window_manager *wm )
{
    if( c <= 0x1F ) return 0;
    if( win->font == 0 )
    {
        return 8;
    }
    else
    {
        utf32_to_win1251( c, c );
        return g_font1[ 1 + c ];
    }
}

int char_y_size( WINDOWPTR win, window_manager *wm )
{
    if( win->font == 0 )
    {
        return 8;
    }
    else
    {
        return g_font1[ 0 ];
    }
}

int string_size( WINDOWPTR win, const UTF8_CHAR *str, window_manager *wm )
{
    int size = 0;
    while( *str )
    {
	UTF32_CHAR c32;
	str += utf8_to_utf32_char( str, &c32 );
	size += char_x_size( win, c32, wm );
    }    
    return size;
}

void win_draw_lock( WINDOWPTR win, window_manager *wm )
{
    wm->screen_changed++;
    device_screen_lock( win, wm );
}

void win_draw_unlock( WINDOWPTR win, window_manager *wm )
{
    device_screen_unlock( win, wm );
}

void win_draw_box( WINDOWPTR win, int x, int y, int xsize, int ysize, COLOR color, window_manager *wm )
{
    if( win && win->visible && win->reg && win->reg->numRects )
    {
	x += win->screen_x;
	y += win->screen_y;
	if( win->reg->numRects )
	{
	    for( int r = 0; r < win->reg->numRects; r++ )
	    {
		int rx1 = win->reg->rects[ r ].left;
		int rx2 = win->reg->rects[ r ].right;
		int ry1 = win->reg->rects[ r ].top;
		int ry2 = win->reg->rects[ r ].bottom;

		//Control box size:
		int nx = x;
		int ny = y;
		int nxsize = xsize;
		int nysize = ysize;
		if( nx < rx1 ) { nxsize -= ( rx1 - nx ); nx = rx1; }
		if( ny < ry1 ) { nysize -= ( ry1 - ny ); ny = ry1; }
		if( nx + nxsize <= rx1 ) continue;
		if( ny + nysize <= ry1 ) continue;
		if( nx + nxsize > rx2 ) nxsize -= nx + nxsize - rx2;
		if( ny + nysize > ry2 ) nysize -= ny + nysize - ry2;
		if( nx >= rx2 ) continue;
		if( ny >= ry2 ) continue;
		if( nxsize < 0 ) continue;
		if( nysize < 0 ) continue;
        	
		//Draw it:
		device_draw_box( nx, ny, nxsize, nysize, color, wm );
	    }
	}
    }
}

void win_draw_frame( WINDOWPTR win, int x, int y, int xsize, int ysize, COLOR color, window_manager *wm )
{
    win_draw_line( win, x, y, x + xsize - 1, y, color, wm );
    win_draw_line( win, x + xsize - 1, y, x + xsize - 1, y + ysize - 1, color, wm );
    win_draw_line( win, x + xsize - 1, y + ysize - 1, x, y + ysize - 1, color, wm );
    win_draw_line( win, x, y + ysize - 1, x, y, color, wm );
}

void win_draw_frame3d( WINDOWPTR win, int x, int y, int xsize, int ysize, COLOR color, int inout, window_manager *wm )
{
    int depth = inout >> 8;
    inout &= 255;
    int tr = 110;
    int wtr = 120;
    COLOR white = wm->white;
    COLOR black = wm->black;
    /*if( depth == 2 )
    {
	win_draw_line( win, x + 1, y + 1, x + xsize - 2, y + 1, blend( color, wm->white, wtr / 2 ), wm );
	win_draw_line( win, x + 1, y + ysize - 2, x + xsize - 2, y + ysize - 2, blend( color, wm->black, tr / 2 ), wm );
    }*/
    for( int l = 0; l < 1; l++ )
    {
	if( inout == 0 )
	{
	    win_draw_line( win, x, y, x + xsize - 1, y, blend( color, black, tr ), wm );
	    win_draw_line( win, x + xsize - 1, y, x + xsize - 1, y + ysize - 1, blend( color, black, tr ), wm );
	    win_draw_line( win, x, y, x, y + ysize - 1, blend( color, white, wtr ), wm );
	    win_draw_line( win, x, y + ysize - 1, x + xsize - 1, y + ysize - 1, blend( color, white, wtr ), wm );
	}
	else
	{
	    win_draw_line( win, x, y, x + xsize - 1, y, blend( color, white, wtr ), wm );
	    win_draw_line( win, x + xsize - 1, y, x + xsize - 1, y + ysize - 1, blend( color, white, wtr ), wm );
	    win_draw_line( win, x, y, x, y + ysize - 1, blend( color, black, tr ), wm );
	    win_draw_line( win, x, y + ysize - 1, x + xsize - 1, y + ysize - 1, blend( color, black, tr ), wm );
	}
	x++; 
	y++;
	xsize -= 2;
	ysize -= 2;
	tr >>= 1;
	wtr >>= 1;
    }
}

void win_draw_bitmap_ext( 
    WINDOWPTR win, 
    int x, 
    int y, 
    int dest_xsize, 
    int dest_ysize,
    int source_x,
    int source_y,
    sundog_image *data, 
    window_manager *wm )
{
    if( source_x < 0 ) { dest_xsize += source_x; x -= source_x; source_x = 0; }
    if( source_y < 0 ) { dest_ysize += source_y; y -= source_y; source_y = 0; }
    if( source_x >= data->xsize ) return;
    if( source_y >= data->ysize ) return;
    if( source_x + dest_xsize > data->xsize ) dest_xsize -= ( source_x + dest_xsize ) - data->xsize;
    if( source_y + dest_ysize > data->ysize ) dest_ysize -= ( source_y + dest_ysize ) - data->ysize;
    if( dest_xsize <= 0 ) return;
    if( dest_ysize <= 0 ) return;
    if( win && win->visible && win->reg && win->reg->numRects )
    {
	x += win->screen_x;
	y += win->screen_y;
	int xsize = dest_xsize;
	int ysize = dest_ysize;
	if( win->reg->numRects )
	{
	    for( int r = 0; r < win->reg->numRects; r++ )
	    {
		int rx1 = win->reg->rects[ r ].left;
		int rx2 = win->reg->rects[ r ].right;
		int ry1 = win->reg->rects[ r ].top;
		int ry2 = win->reg->rects[ r ].bottom;

		//Control box size:
		int src_x = source_x;
		int src_y = source_y;
		int nx = x;
		int ny = y;
		int nxsize = xsize;
		int nysize = ysize;
		if( nx < rx1 ) { nxsize -= ( rx1 - nx ); src_x += ( rx1 - nx ); nx = rx1; }
		if( ny < ry1 ) { nysize -= ( ry1 - ny ); src_y += ( ry1 - ny ); ny = ry1; }
		if( nx + nxsize <= rx1 ) continue;
		if( ny + nysize <= ry1 ) continue;
		if( nx + nxsize > rx2 ) nxsize -= nx + nxsize - rx2;
		if( ny + nysize > ry2 ) nysize -= ny + nysize - ry2;
		if( nx >= rx2 ) continue;
		if( ny >= ry2 ) continue;
		if( nxsize < 0 ) continue;
		if( nysize < 0 ) continue;
        	
		//Draw it:
		device_draw_bitmap( nx, ny, nxsize, nysize, src_x, src_y, data, wm );
	    }
	}
    }
}

void win_draw_bitmap( 
    WINDOWPTR win, 
    int x, 
    int y, 
    sundog_image *data, 
    window_manager *wm )
{
    if( win && win->visible && win->reg && win->reg->numRects )
    {
	x += win->screen_x;
	y += win->screen_y;
	if( win->reg->numRects )
	{
	    for( int r = 0; r < win->reg->numRects; r++ )
	    {
		int rx1 = win->reg->rects[ r ].left;
		int rx2 = win->reg->rects[ r ].right;
		int ry1 = win->reg->rects[ r ].top;
		int ry2 = win->reg->rects[ r ].bottom;

		//Control box size:
		int src_x = 0;
		int src_y = 0;
		int nx = x;
		int ny = y;
		int nxsize = data->xsize;
		int nysize = data->ysize;
		if( nx < rx1 ) { nxsize -= ( rx1 - nx ); src_x += ( rx1 - nx ); nx = rx1; }
		if( ny < ry1 ) { nysize -= ( ry1 - ny ); src_y += ( ry1 - ny ); ny = ry1; }
		if( nx + nxsize <= rx1 ) continue;
		if( ny + nysize <= ry1 ) continue;
		if( nx + nxsize > rx2 ) nxsize -= nx + nxsize - rx2;
		if( ny + nysize > ry2 ) nysize -= ny + nysize - ry2;
		if( nx >= rx2 ) continue;
		if( ny >= ry2 ) continue;
		if( nxsize < 0 ) continue;
		if( nysize < 0 ) continue;
        	
		//Draw it:
		device_draw_bitmap( nx, ny, nxsize, nysize, src_x, src_y, data, wm );
	    }
	}
    }
}

void win_draw_char( WINDOWPTR win, UTF32_CHAR c, int x, int y, COLOR f, COLOR b, window_manager *wm )
{
    if( c < 0x10 ) return; //Control symbols
    //Default SunDog font is win1251.
    //Convert from UTF32 to WIN1251:
    utf32_to_win1251( c, c );
    //Draw:
    uchar xsize;
    uchar ysize;
    uchar *font;
    if( win->font == 0 )
    {
	font = g_font0;
	xsize = 8;
	ysize = 8;
    }
    else
    {
	font = g_font1;
	xsize = font[ 1 + c ];
	ysize = font[ 0 ];
    }
#if defined(OPENGL) && !defined(FRAMEBUFFER)
    sundog_image *font_img;
    if( win->font == 0 )
    {
    	font_img = g_font0_img;
    }
    else
    {
	font_img = g_font1_img;
    }
    font_img->color = b;
    font_img->transp = 255;
    win_draw_bitmap_ext( 
        win, 
        x, y, 
        xsize, ysize,
        0, 255 * 8,
        font_img, 
        wm );
    font_img->color = f;
    win_draw_bitmap_ext( 
        win, 
        x, y, 
        xsize, ysize,
        0, c * 8,
        g_font0_img, 
        wm );
#else
    sundog_image tmp_img;
    tmp_img.xsize = xsize;
    tmp_img.ysize = ysize;
    tmp_img.flags = IMAGE_NATIVE_RGB | IMAGE_STATIC_SOURCE;
    COLOR bmp[ 8 * 12 ];
    tmp_img.data = bmp;
    c *= ysize;
    int ptr = 0;
    font += 257;
    for( int l = 0; l < ysize; l++ )
    {
        uchar v = font[ c ]; c++;
	for( int x = 0; x < xsize; x++ )
	{
	    if( v & 0x80 )
	        bmp[ ptr ] = f;
	    else
	        bmp[ ptr ] = b;
	    v <<= 1;
	    ptr++;
	}
    }
    win_draw_bitmap( win, x, y, &tmp_img, wm );
#endif
}

void win_draw_string( WINDOWPTR win, const UTF8_CHAR *str, int x, int y, COLOR f, COLOR b, window_manager *wm )
{
    int start_x = x;
    while( *str )
    {
	if( *str == 0xA ) 
	{ 
	    y += char_y_size( win, wm ); 
	    x = start_x; 
	    str++;
	}
	else
	{
	    UTF32_CHAR c32;
	    str += utf8_to_utf32_char( str, &c32 );
	    win_draw_char( win, c32, x, y, f, b, wm );
	    x += char_x_size( win, c32, wm );
	}
    }
}

#define cbottom 1
#define ctop 2
#define cleft 4
#define cright 8
int make_code( int x, int y, int clip_x1, int clip_y1, int clip_x2, int clip_y2 )
{
    int code = 0;
    if( y >= clip_y2 ) code = cbottom;
    else if( y < clip_y1 ) code = ctop;
    if( x >= clip_x2 ) code += cright;
    else if( x < clip_x1 ) code += cleft;
    return code;
}

void draw_line2( 
    int x1, int y1,
    int x2, int y2, 
    int clip_x1, int clip_y1,
    int clip_x2, int clip_y2,
    COLOR color, window_manager *wm )
{
    //Cohen-Sutherland line clipping algorithm:
    int code0;
    int code1;
    int out_code;
    int x, y;
    code0 = make_code( x1, y1, clip_x1, clip_y1, clip_x2, clip_y2 );
    code1 = make_code( x2, y2, clip_x1, clip_y1, clip_x2, clip_y2 );
    while( code0 || code1 )
    {
	if( code0 & code1 ) return; //Trivial reject
	else
	{
	    //Failed both tests, so calculate the line segment to clip
	    if( code0 )
		out_code = code0; //Clip the first point
	    else
		out_code = code1; //Clip the last point

	    if( out_code & cbottom )
	    {
		//Clip the line to the bottom of the viewport
		y = clip_y2 - 1;
		x = x1 + ( x2 - x1 ) * ( y - y1 ) / ( y2 - y1 );
	    }
	    else 
	    if( out_code & ctop )
	    {
		y = clip_y1;
		x = x1 + ( x2 - x1 ) * ( y - y1 ) / ( y2 - y1 );
	    }
	    else
	    if( out_code & cright )
	    {
		x = clip_x2 - 1;
		y = y1 + ( y2 - y1 ) * ( x - x1 ) / ( x2 - x1 );
	    }
	    else
	    if( out_code & cleft )
	    {
		x = clip_x1;
		y = y1 + ( y2 - y1 ) * ( x - x1 ) / ( x2 - x1 );
	    }

	    if( out_code == code0 )
	    { //Modify the first coordinate 
		x1 = x; y1 = y;
		code0 = make_code( x1, y1, clip_x1, clip_y1, clip_x2, clip_y2 );
	    }
	    else
	    { //Modify the second coordinate
		x2 = x; y2 = y;
		code1 = make_code( x2, y2, clip_x1, clip_y1, clip_x2, clip_y2 );
	    }
	}
    }

    //Draw line:
    device_draw_line( x1, y1, x2, y2, color, wm );
}

void win_draw_line( WINDOWPTR win, int x1, int y1, int x2, int y2, COLOR color, window_manager *wm )
{
    if( win && win->visible && win->reg && win->reg->numRects )
    {
	x1 += win->screen_x;
	y1 += win->screen_y;
	x2 += win->screen_x;
	y2 += win->screen_y;
	if( win->reg->numRects )
	{
	    for( int r = 0; r < win->reg->numRects; r++ )
	    {
		int rx1 = win->reg->rects[ r ].left;
		int rx2 = win->reg->rects[ r ].right;
		int ry1 = win->reg->rects[ r ].top;
		int ry2 = win->reg->rects[ r ].bottom;

		draw_line2( x1, y1, x2, y2, rx1, ry1, rx2, ry2, color, wm );
	    }
	}
    }
}

//###################################
//### DIALOGS:                    ###
//###################################

UTF8_CHAR dialog_filename[ 1024 ];

UTF8_CHAR *dialog_open_file( const UTF8_CHAR *name, const UTF8_CHAR *mask, const UTF8_CHAR *id, window_manager *wm )
{
    WINDOWPTR prev_focus = wm->focus_win;

    UTF8_CHAR *path = get_user_path();
    int path_len = mem_strlen( (const UTF8_CHAR*)path );
    int id_len = mem_strlen( id );
    UTF8_CHAR *id2 = (UTF8_CHAR*)MEM_NEW( HEAP_DYNAMIC, path_len + id_len + 1 );
    id2[ 0 ] = 0;
    mem_strcat( id2, (const UTF8_CHAR*)path );
    mem_strcat( id2, id );

    FILES_PROPS = (const UTF8_CHAR*)id2;
    FILES_MASK = mask;
    FILES_RESULTED_FILENAME = dialog_filename;
    WINDOWPTR win = new_window_with_decorator( 
	name, 
	0, 0, 
	340, 320, 
	wm->dialog_color,
	wm->root_win, 
	files_handler,
	DECOR_FLAG_CENTERED | DECOR_FLAG_CHECK_SIZE,
	wm );
    show_window( win, wm );
    recalc_regions( wm );
    draw_window( win, wm );

    while( 1 )
    {
	sundog_event evt;
	EVENT_LOOP_BEGIN( &evt, wm );
	if( EVENT_LOOP_END( wm ) ) break;
	if( win->visible == 0 ) break;
    }

    set_focus_win( prev_focus, wm );
    
    mem_free( id2 );

    if( dialog_filename[ 0 ] == 0 ) return 0;
    return dialog_filename;
}

int dialog( const UTF8_CHAR *name, const UTF8_CHAR *buttons, window_manager *wm )
{
    WINDOWPTR prev_focus = wm->focus_win;
    int result = 0;

    DIALOG_BUTTONS_TEXT = buttons;
    DIALOG_TEXT = name;
    DIALOG_RESULT = &result;
    WINDOWPTR win = new_window_with_decorator( 
	"", 
	0, 0, 
	320, 190,
	wm->dialog_color, 
	wm->root_win, 
	dialog_handler,
	DECOR_FLAG_CENTERED | DECOR_FLAG_CHECK_SIZE,
	wm );
    show_window( win, wm );
    recalc_regions( wm );
    draw_window( win, wm );

    while( 1 )
    {
	sundog_event evt;
	EVENT_LOOP_BEGIN( &evt, wm );
	if( EVENT_LOOP_END( wm ) ) break;
	if( win->visible == 0 ) break;
    }

    set_focus_win( prev_focus, wm );

    return result;
}

//###################################
//### DEVICE DEPENDENT FUNCTIONS: ###
//###################################

// device_start(), device_end() and device_event_handler() :

void fix_fullscreen_resolution( int *xsize, int *ysize, window_manager *wm )
{
    if( *xsize <= 320 ) { *xsize = 320; *ysize = 200; }
    else if( *xsize <= 640 ) { *xsize = 640; *ysize = 480; }
    else if( *xsize <= 800 ) { *xsize = 800; *ysize = 600; }
    else if( *xsize <= 1024 ) { *xsize = 1024; *ysize = 768; }
    else if( *xsize <= 1152 ) { *xsize = 1152; *ysize = 864; }
    else if( *xsize <= 1280 ) 
    {
	*xsize = 1280;
	if( *ysize <= 720 ) *ysize = 720;
	else if( *ysize <= 768 ) *ysize = 768;
	else if( *ysize <= 960 ) *ysize = 960;
	else if( *ysize <= 1024 ) *ysize = 1024;
    }
}

#ifndef FRAMEBUFFER
#endif

#ifndef NONPALM
    #include "wm_palmos.h"
#endif

#ifdef WIN
    #include "wm_win32.h"
#endif

#ifdef WINCE
    #include "wm_wince.h"
#endif

#ifdef UNIX
    #if defined(OPENGL) || defined(X11)
	#include "wm_unixgraphics.h"
    #endif
    #ifdef DIRECTDRAW
	#include "wm_unixgraphics_sdl.h"
    #endif
#endif

//###################################
//###################################
//###################################
